package mit.edu.concurrencyrefactorings.refactorings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.InfixExpression.Operator;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.corext.SourceRange;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.text.edits.TextEditGroup;
public class AccessAnalyzerForAtomicLong extends ASTVisitor {
private static final String READ_ACCESS = "Read Access";
private static final String WRITE_ACCESS = "Write Access";
private static final String POSTFIX_ACCESS = "Postfix Access";
private static final String PREFIX_ACCESS = "Prefix Access";
private static final String REMOVE_SYNCHRONIZED_MODIFIER = "Remove Synchronized Modifier";
private static final String REMOVE_SYNCHRONIZED_BLOCK = "Remove Synchronized Block";
private ICompilationUnit fCUnit;
private IVariableBinding fFieldBinding;
private ITypeBinding fDeclaringClassBinding;
private ASTRewrite fRewriter;
private ImportRewrite fImportRewriter;
private List<TextEditGroup> fGroupDescriptions;
private boolean fIsFieldFinal;
private RefactoringStatus fStatus;
private boolean fSetterMustReturnValue;
public AccessAnalyzerForAtomicLong(
ConvertToAtomicLongRefactoring refactoring,
ICompilationUnit unit, IVariableBinding field,
ITypeBinding declaringClass, ASTRewrite rewriter,
ImportRewrite importRewrite) {
fCUnit= unit;
fFieldBinding= field.getVariableDeclaration();
fDeclaringClassBinding= declaringClass;
fRewriter= rewriter;
fImportRewriter= importRewrite;
fGroupDescriptions= new ArrayList<TextEditGroup>();
try {
fIsFieldFinal= Flags.isFinal(refactoring.getField().getFlags());
} catch (JavaModelException e) {
// assume non final field
}
fStatus= new RefactoringStatus();
}
public RefactoringStatus getStatus() {
return fStatus;
}
public Collection getGroupDescriptions() {
return fGroupDescriptions;
}
public boolean visit(SimpleName node) {
Expression invocation = null;
if (!node.isDeclaration()
&& considerBinding(resolveBinding(node))) {
invocation = (MethodInvocation) fRewriter.createStringPlaceholder(
fFieldBinding.getName() + ".get()", ASTNode.METHOD_INVOCATION);
if (!(checkSynchronizedBlock(node, invocation, READ_ACCESS) ||
checkSynchronizedMethod(node, invocation, READ_ACCESS))) {
fRewriter.replace(node, invocation,createGroupDescription(READ_ACCESS));
}
}
return true;
}
public boolean visit(Assignment node) {
boolean needToVisitRHS = true;
Expression lhs= node.getLeftHandSide();
if (!considerBinding(resolveBinding(lhs)))
return true;
if (! checkParent(node)) {
//TODO implement the case `return i = 12;' => i.set(12); return i.get();
}
if (!fIsFieldFinal) {
// Write access.
AST ast= node.getAST();
MethodInvocation invocation= ast.newMethodInvocation();
invocation.setName(ast.newSimpleName("set"));
Expression receiver= getReceiver(lhs);
if (receiver != null) {
// VIP!! Here we use node.coySubtree because the expresion/arguments might be overriden by the later code.
// If they are overriden later, using rewriter.createCopyTarget() will result in an orphan CopySourceEdit
// without a matching CoypTargetEdit. This would lead later to a MalformedTreeException
invocation.setExpression((Expression)ASTNode.copySubtree(ast, receiver));
}
List arguments= invocation.arguments();
// VIP!! Here we use node.coySubtree because the expresion/arguments might be overriden by the later code.
// If they are overriden later, using rewriter.createCopyTarget() will result in an orphan CopySourceEdit
// without a matching CoypTargetEdit. This would lead later to a MalformedTreeException
Expression copyRHS = (Expression) ASTNode.copySubtree(ast, node.getRightHandSide());
if (node.getOperator() == Assignment.Operator.ASSIGN) {
Expression rightHandSide = node.getRightHandSide();
if (rightHandSide instanceof InfixExpression) {
InfixExpression infixExpression = (InfixExpression) rightHandSide;
Expression leftOperand = infixExpression.getLeftOperand();
if ((considerBinding(resolveBinding(leftOperand)))) {
Operator operator = infixExpression.getOperator();
if (operator == InfixExpression.Operator.PLUS) {
needToVisitRHS = false;
invocation.setName(ast.newSimpleName("addAndGet"));
arguments.add((Expression) fRewriter.createCopyTarget(infixExpression.getRightOperand()));
} else if (operator == InfixExpression.Operator.MINUS) {
needToVisitRHS = false;
invocation.setName(ast.newSimpleName("addAndGet"));
arguments.add(createNegativeExpression(infixExpression.getRightOperand()));
} else {
createErrorStatus(node);
}
}
}
if (needToVisitRHS)
arguments.add(copyRHS);
}
if (node.getOperator() != Assignment.Operator.ASSIGN) {
if (node.getOperator() == Assignment.Operator.PLUS_ASSIGN) {
arguments.add(copyRHS);
// This is the compound assignment case: field+= 10; =>
// field.addAndGet(10);
invocation.setName(ast.newSimpleName("addAndGet"));
} else if (node.getOperator() == Assignment.Operator.MINUS_ASSIGN) {
// This is the compound assignment case: field -= 10; =>
// field.addAndGet(-10);
invocation.setName(ast.newSimpleName("addAndGet"));
PrefixExpression negativeExpression = createNegativeExpression(node.getRightHandSide());
arguments.add(negativeExpression);
} else {
createErrorStatus(node);
}
}
if( !(checkSynchronizedBlock(node, invocation, WRITE_ACCESS) || checkSynchronizedMethod(node, invocation, WRITE_ACCESS)) ) {
fRewriter.replace(node, invocation, createGroupDescription(WRITE_ACCESS));
}
}
if (needToVisitRHS)
node.getRightHandSide().accept(this);
return false;
}
private void createErrorStatus(Assignment node) {
fStatus = new RefactoringStatus();
fStatus.addError("Cannot execute "
+ fFieldBinding.getName()
+ ".set("
+ fFieldBinding.getName()
+ ".get()) "
+ "atomically. This would be required in converting "
+ node.toString()
+ ". Consider using locks instead.");
}
private void createErrorStatus(String message) {
fStatus = new RefactoringStatus();
fStatus.addError(message);
}
private PrefixExpression createNegativeExpression(Expression expression) {
AST ast = expression.getAST();
PrefixExpression newPrefixExpression = ast.newPrefixExpression();
newPrefixExpression.setOperator(PrefixExpression.Operator.MINUS);
boolean needsParentheses=
//ASTNodes.needsParentheses(expression);
(new Object() {
public boolean needsParentheses(Expression expression) {
int type= expression.getNodeType();
return type == ASTNode.INFIX_EXPRESSION || type == ASTNode.CONDITIONAL_EXPRESSION ||
type == ASTNode.PREFIX_EXPRESSION || type == ASTNode.POSTFIX_EXPRESSION ||
type == ASTNode.CAST_EXPRESSION || type == ASTNode.INSTANCEOF_EXPRESSION;
}
}).needsParentheses(expression);
ASTNode copyExpression = ASTNode.copySubtree(ast, expression); //fRewriter.createCopyTarget(expression);
if (needsParentheses) {
ParenthesizedExpression p = ast.newParenthesizedExpression();
p.setExpression((Expression) copyExpression);
copyExpression = p;
}
newPrefixExpression.setOperand((Expression) copyExpression);
return newPrefixExpression;
}
public boolean visit(PostfixExpression expression)
{
Expression operand= expression.getOperand();
PostfixExpression.Operator operator = expression.getOperator();
if (!considerBinding(resolveBinding(operand)))
return true;
ASTNode parent= expression.getParent();
// if (!(parent instanceof ExpressionStatement)) {
// fStatus.addError("Cannot convert postfix expression",
// JavaStatusContext.create(fCUnit, new SourceRange(expression)));
// return false;
// }
AST ast= expression.getAST();
MethodInvocation invocation= ast.newMethodInvocation();
invocation.setExpression((Expression)fRewriter.createCopyTarget(operand));
if(operator == PostfixExpression.Operator.INCREMENT) {
invocation.setName(ast.newSimpleName("getAndIncrement"));
}
else if(operator == PostfixExpression.Operator.DECREMENT) {
invocation.setName(ast.newSimpleName("getAndDecrement"));
}
if( !(checkSynchronizedBlock(expression, invocation, POSTFIX_ACCESS) || checkSynchronizedMethod(expression, invocation, POSTFIX_ACCESS)) ) {
fRewriter.replace(expression, invocation, createGroupDescription(POSTFIX_ACCESS));
}
return false;
}
public boolean visit(PrefixExpression expression)
{
Expression operand= expression.getOperand();
PrefixExpression.Operator operator = expression.getOperator();
if (!considerBinding(resolveBinding(operand)))
return true;
// ASTNode parent= expression.getParent();
// if (!(parent instanceof ExpressionStatement)) {
// fStatus.addError("Cannot convert prefix expression",
// JavaStatusContext.create(fCUnit, new SourceRange(expression)));
// return false;
// }
AST ast= expression.getAST();
MethodInvocation invocation= ast.newMethodInvocation();
invocation.setExpression((Expression)fRewriter.createCopyTarget(operand));
if(operator == PrefixExpression.Operator.INCREMENT) {
invocation.setName(ast.newSimpleName("incrementAndGet"));
}
else if(operator == PrefixExpression.Operator.DECREMENT) {
invocation.setName(ast.newSimpleName("decrementAndGet"));
}
if( !(checkSynchronizedBlock(expression, invocation, PREFIX_ACCESS) || checkSynchronizedMethod(expression, invocation, PREFIX_ACCESS)) ) {
fRewriter.replace(expression, invocation, createGroupDescription(PREFIX_ACCESS));
}
return false;
}
public void endVisit(CompilationUnit node) {
fImportRewriter.addImport("java.util.concurrent.atomic.AtomicLong");
}
private boolean checkSynchronizedBlock(ASTNode node, Expression invocation, String accessType) {
AST ast = node.getAST();
ASTNode syncStatement = ASTNodes.getParent(node, SynchronizedStatement.class);
if(syncStatement != null) {
Block syncBody = ((SynchronizedStatement)syncStatement).getBody();
List syncBodyStatements = syncBody.statements();
if(syncBodyStatements.size() > 1) {
fRewriter.replace(node, invocation, createGroupDescription(accessType));
} else {
ExpressionStatement newExpressionStatement = ast.newExpressionStatement(invocation);
fRewriter.replace(syncStatement, newExpressionStatement, createGroupDescription(REMOVE_SYNCHRONIZED_BLOCK));
}
return true;
}
return false;
}
private boolean checkSynchronizedMethod(ASTNode node,
Expression invocation, String accessType) {
MethodDeclaration methodDecl = (MethodDeclaration) ASTNodes.getParent(node, MethodDeclaration.class);
int modifiers = methodDecl.getModifiers();
if(Modifier.isSynchronized(modifiers)) {
List methodBodyStatements = methodDecl.getBody().statements();
if(methodBodyStatements.size() == 1) {
ModifierRewrite methodRewriter = ModifierRewrite.create(fRewriter, methodDecl);
int synchronized1 = Modifier.SYNCHRONIZED;
synchronized1 = ~ synchronized1;
int newModifiersWithoutSync = modifiers & synchronized1;
methodRewriter.setModifiers(newModifiersWithoutSync, createGroupDescription(REMOVE_SYNCHRONIZED_MODIFIER));
}
fRewriter.replace(node, invocation, createGroupDescription(accessType));
return true;
}
return false;
}
private Expression getReceiver(Expression expression) {
int type= expression.getNodeType();
switch(type) {
case ASTNode.SIMPLE_NAME:
return expression;
case ASTNode.QUALIFIED_NAME:
return ((QualifiedName)expression).getQualifier();
case ASTNode.FIELD_ACCESS:
return expression;
case ASTNode.SUPER_FIELD_ACCESS:
return expression;
case ASTNode.THIS_EXPRESSION:
return expression;
}
return null;
}
private IBinding resolveBinding(Expression expression) {
if (expression instanceof SimpleName)
return ((SimpleName)expression).resolveBinding();
else if (expression instanceof QualifiedName)
return ((QualifiedName)expression).resolveBinding();
else if (expression instanceof FieldAccess)
return ((FieldAccess)expression).getName().resolveBinding();
else if (expression instanceof SuperFieldAccess)
return ((SuperFieldAccess)expression).getName().resolveBinding();
return null;
}
private TextEditGroup createGroupDescription(String name) {
TextEditGroup result= new TextEditGroup(name);
fGroupDescriptions.add(result);
return result;
}
private boolean considerBinding(IBinding binding /*, ASTNode node*/) {
if (!(binding instanceof IVariableBinding))
return false;
boolean result= Bindings.equals(fFieldBinding, ((IVariableBinding)binding).getVariableDeclaration());
//if (!result)
return result;
// if (binding instanceof IVariableBinding) {
// AbstractTypeDeclaration type= (AbstractTypeDeclaration)ASTNodes.getParent(node, AbstractTypeDeclaration.class);
// if (type != null) {
// ITypeBinding declaringType= type.resolveBinding();
// return Bindings.equals(fDeclaringClassBinding, declaringType);
// }
// }
// return true;
}
private boolean checkParent(ASTNode node) {
ASTNode parent = node.getParent();
return parent instanceof ExpressionStatement;
}
}
// TODO (implementation and tests)
// How to handle:
// f *= 10; f = f * 10;
// f /= 10; f = f / 10;
// f %= 10; f = f % 10;
// f &= 10; f = f & 10;
// f |= 10; f = f | 10;